package io.hellsinger.vortex.ui.component

import android.content.Context
import android.graphics.Canvas
import android.text.InputType
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputMethodManager
import io.hellsinger.vortex.component.gesture.MotionTracker
import io.hellsinger.vortex.component.layout.DefaultTextLayout
import io.hellsinger.vortex.data.Line
import kotlin.math.max
import kotlin.math.min

class StorageTextEditorView : View {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr,
    )

    var inputType = 0

    private val renderer = StorageTextEditorRenderer(target = this)
    private val tracker = MotionTracker(target = this)
    private var layout = DefaultTextLayout()

    private val inputMethodManager =
        context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager

    private val connection = EditorInputConnection(target = this)

    private var horizontalScrollRange = 0
    private var verticalScrollRange = 0
    private var linesPerExtent = computeVerticalScrollExtent() / renderer.lineHeight

    // Unused
    private var symbolsPerExtent = 0

    private var flags: Int = CAN_SCROLL_VERTICALLY

    val layoutWidth: Int
        get() = layout.width

    val layoutHeight: Int
        get() = layout.height

    val layoutMaxLineIndex
        get() = layout.maxTextIndex

    val lines: List<Line>
        get() = layout.lines()

    val firstVisibleLineIndex: Int
        get() = max(0, computeVerticalScrollOffset() / renderer.lineHeight)

    val lastVisibleLineIndex
        get() = min(layout.lineCount - 1, firstVisibleLineIndex + linesPerExtent)

    var canScrollHorizontally: Boolean
        get() = flags and CAN_SCROLL_HORIZONTALLY == CAN_SCROLL_HORIZONTALLY
        set(value) {
            flags =
                if (value) {
                    flags or CAN_SCROLL_HORIZONTALLY
                } else {
                    flags and CAN_SCROLL_HORIZONTALLY.inv()
                }
        }

    var canScrollVertically: Boolean
        get() = flags and CAN_SCROLL_VERTICALLY == CAN_SCROLL_VERTICALLY
        set(value) {
            flags =
                if (value) {
                    flags or CAN_SCROLL_VERTICALLY
                } else {
                    flags and CAN_SCROLL_VERTICALLY.inv()
                }
        }

    fun addFlags(flags: Int) {
        this.flags = this.flags or flags
    }

    fun removeFlags(flags: Int) {
        this.flags = this.flags and flags.inv()
    }

    override fun onDraw(canvas: Canvas) {
        renderer.onDraw(canvas)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (!isEnabled) return false

        return tracker.onMotion(event)
    }

    fun appendLine(line: String) {
        layout.appendLine(Line(line))
        updateCacheValues()
        invalidate()
    }

    suspend fun setLines(lines: List<Line>) {
        layout.setLines(lines)
        updateCacheValues()
        invalidate()
    }

    private fun updateCacheValues() {
        horizontalScrollRange = max(width, layout.width - width)
        verticalScrollRange = max(height, renderer.lineHeight * lines.size - height)
        // Visible area / line height = visible line count
        linesPerExtent = computeVerticalScrollExtent() / renderer.lineHeight
        renderer.measureLineCountWidth(lines.size)
    }

    fun showSoftwareKeyboard(): Boolean {
        requestFocus()
        return inputMethodManager.showSoftInput(this, 0)
    }

    fun hideSoftwareKeyboard(): Boolean = inputMethodManager.hideSoftInputFromWindow(windowToken, 0)

    override fun onLayout(
        changed: Boolean,
        left: Int,
        top: Int,
        right: Int,
        bottom: Int,
    ) {
        super.onLayout(changed, left, top, right, bottom)
        updateCacheValues()
    }

    public override fun computeHorizontalScrollOffset(): Int = super.computeHorizontalScrollOffset()

    public override fun computeHorizontalScrollExtent(): Int = super.computeHorizontalScrollExtent()

    public override fun computeHorizontalScrollRange(): Int = horizontalScrollRange

    public override fun computeVerticalScrollOffset(): Int = super.computeVerticalScrollOffset()

    public override fun computeVerticalScrollExtent(): Int = super.computeVerticalScrollExtent()

    public override fun computeVerticalScrollRange(): Int = verticalScrollRange

    override fun computeScroll() {
        if (tracker.computeScrollOffset()) {
            val oldX = scrollX
            val oldY = scrollY
            val currentHorizontalScrollOffset = tracker.horizontalScrollOffset
            val currentVerticalScrollOffset = tracker.verticalScrollOffset

            scrollTo(currentHorizontalScrollOffset, currentVerticalScrollOffset)

            if (currentVerticalScrollOffset < 0 && oldY >= 0) {
                renderer.onAbsorbTopScrollOffset(tracker.scrollVelocity.toInt())
            } else if (currentVerticalScrollOffset > verticalScrollRange && oldY <= verticalScrollRange) {
                renderer.onAbsorbBottomScrollOffset(tracker.scrollVelocity.toInt())
            }

            if (currentHorizontalScrollOffset < 0 && oldX >= 0) {
                renderer.onAbsorbLeftScrollOffset(tracker.scrollVelocity.toInt())
            } else if (currentHorizontalScrollOffset > horizontalScrollRange && oldX <= horizontalScrollRange) {
                renderer.onAbsorbRightScrollOffset(tracker.scrollVelocity.toInt())
            }

            // Continue scroll animation
            postInvalidateOnAnimation()
        }
    }

    internal fun onPullScrollOffset(
        oldX: Int,
        oldY: Int,
        horizontalScrollDistance: Float,
        verticalScrollDistance: Float,
        horizontalDisplacement: Float,
        verticalDisplacement: Float,
    ) {
        renderer.onPullScrollOffset(
            oldX = oldX,
            oldY = oldY,
            horizontalScrollDistance = horizontalScrollDistance,
            verticalScrollDistance = verticalScrollDistance,
            horizontalDisplacement = horizontalDisplacement,
            verticalDisplacement = verticalDisplacement,
        )
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        tracker.onAttach()
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        tracker.onDetach()
    }

    fun destroy() {
        layout.close()
    }

    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
        if (!isEnabled) return null

        if (inputType != 0) {
            outAttrs.inputType = inputType
        } else {
            outAttrs.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
        }

        outAttrs.initialSelStart = 0
        outAttrs.initialSelEnd = 0

        return connection
    }

    companion object {
        // Scroll
        const val CAN_SCROLL_HORIZONTALLY = 1 shl 0
        const val CAN_SCROLL_VERTICALLY = 1 shl 1

        // Line number
        const val PIN_LINE_NUMBER_HORIZONTALLY = 1 shl 2

        // Scale
        const val CAN_SCALE = 1 shl 3
    }
}
